/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed partial class TopicFiltersListControl : UserControl
	{
		#region Public Properties
		[Browsable(false), DefaultValue(null), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ITopicFilter SelectedTopicFilter
		{
			get
			{
				DataGridViewRow row = SelectedRow;

				if (row == null)
					return null;
				else
					return (ITopicFilter) row.DataBoundItem;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (!filters.Contains(value))
					throw new ArgumentException(Resources.Errors.TopicFilterNotContained, "value");

				filtersDataGridView.Focus();
				filtersDataGridView.ClearSelection();
				filtersDataGridView.Rows[filters.IndexOf(value)].Selected = true;
			}
		}

		[Category("Behavior"), DefaultValue(true),
		Description("Indicates whether the Apply All button and individual Apply buttons on filters are enabled.")]
		public bool ApplyEnabled
		{
			get
			{
				return applyEnabled;
			}
			set
			{
				applyEnabled = value;
				applyButtonColumn.Visible = value;
				loadButtonColumn.Visible = value;

				UpdateApplyAllButton();
			}
		}
		#endregion

		#region Private / Protected
		private DataGridViewRow SelectedRow
		{
			get
			{
				if (filtersDataGridView.SelectedRows.Count == 1)
				{
					DataGridViewRow row = filtersDataGridView.SelectedRows[0];

					if (row != null && row.Index > -1)
						return row;
				}

				return null;
			}
		}

		private TopicFilterCollection filters;
		private bool updatingBuildAllCheckBoxState, updatingBuildStates, applyEnabled = true;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicFiltersListControl" /> class.
		/// </summary>
		public TopicFiltersListControl()
		{
			InitializeComponent();
		}
		#endregion

		#region Methods
		public void EndEdits()
		{
			filtersDataGridView.EndEdit();
		}

		public void SuspendChangeTracking()
		{
			if (filters != null)
				filters.SuspendChangeTacking();
		}

		public void ResumeChangeTracking()
		{
			if (filters != null)
				filters.ResumeChangeTracking(true);
		}

		public void ClearFilters()
		{
			if (filters != null)
			{
				filters.ItemPropertyChanged -= filters_ItemPropertyChanged;
				source.DataSource = filters = null;
			}
		}

		public void LoadFilters(TopicFilterCollection topicFilters)
		{
			if (topicFilters == null)
				throw new ArgumentNullException("topicFilters");

			if (filters != null)
				filters.ItemPropertyChanged -= filters_ItemPropertyChanged;

			source.DataSource = filters = topicFilters;

			filters.ItemPropertyChanged += filters_ItemPropertyChanged;

			bool autoApplyAll = true;

			foreach (ITopicFilter filter in filters)
			{
				if (!filter.AutoApply)
				{
					autoApplyAll = false;
					break;
				}
			}

			updatingBuildAllCheckBoxState = true;

			buildAllCheckBox.Checked = autoApplyAll;

			updatingBuildAllCheckBoxState = false;
		}

		private void ImportFilters()
		{
			using (OpenFileDialog dialog = new OpenFileDialog())
			{
				dialog.AddExtension = true;
				dialog.CheckFileExists = true;
				dialog.CheckPathExists = true;
				dialog.DefaultExt = ".xml";
				dialog.Filter = "Dynamic Filter Files (*.xml)|*.xml";
				dialog.Title = "Import Dynamic Filters";

				if (dialog.ShowDialog(DocProjectEnvironment.MainWindow) == DialogResult.OK)
				{
					try
					{
						filters.AddRange(TopicFilter.LoadFilters(dialog.FileName));
					}
					catch (System.IO.IOException ex)
					{
						Log.Exception(ex, Resources.Errors.InvalidTopicFiltersFile, dialog.FileName);

						DocProjectEnvironment.ShowErrorDialogWithCaption(Resources.Text.InvalidTopicFiltersFileTitle, 
							Resources.Errors.InvalidTopicFiltersFile, dialog.FileName, Environment.NewLine, ex.Message);
					}
					catch (System.Xml.XmlException ex)
					{
						Log.Exception(ex, Resources.Errors.InvalidTopicFiltersFile, dialog.FileName);

						DocProjectEnvironment.ShowErrorDialogWithCaption(Resources.Text.InvalidTopicFiltersFileTitle,
							Resources.Errors.InvalidTopicFiltersFile, dialog.FileName, Environment.NewLine, ex.Message);
					}

					UpdateBuildAllCheckBox();
				}
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void ImportFilters(IEnumerable<Type> filterTypes)
		{
			foreach (Type type in filterTypes)
			{
				try
				{
					filters.Add((ITopicFilter) Activator.CreateInstance(type));
				}
				catch (Exception ex)
				{
					DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.CannotCreateFilterType, type.FullName);
				}
			}
		}

		private void MoveSelectedFilterUp()
		{
			ITopicFilter filter = SelectedTopicFilter;

			// NOTE: The following code that retrieves and uses the selected row index is required because the 
			// TopicFilterCollection no longer uses the ItemMoved notification since it caused unexpected behavior 
			// in the DataGridView.  See the comments in the TopicFilterCollection.MoveUp method for more info.
			DataGridViewRow row = SelectedRow;
			int index = (row != null) ? row.Index : -1; 	// index must be retrieved before MoveUp is called.

			if (filter != null)
				filters.MoveUp(filter);

			if (index > -1)
				filtersDataGridView.Rows[index - 1].Selected = true;
		}

		private void MoveSelectedFilterDown()
		{
			ITopicFilter filter = SelectedTopicFilter;

			// NOTE: The following code that retrieves and uses the selected row index is required because the 
			// TopicFilterCollection no longer uses the ItemMoved notification since it caused unexpected behavior 
			// in the DataGridView.  See the comments in the TopicFilterCollection.MoveDown method for more info.
			DataGridViewRow row = SelectedRow;
			int index = (row != null) ? row.Index : -1;		// index must be retrieved before MoveDown is called.

			if (filter != null)
				filters.MoveDown(filter);

			if (index > -1)
				filtersDataGridView.Rows[index + 1].Selected = true;
		}

		private void UpdateForSelectedRow()
		{
			DataGridViewRow row = SelectedRow;

			deleteButton.Enabled = row != null;
			upButton.Enabled = row != null && row.Index > 0;
			downButton.Enabled = row != null && row.Index < filters.Count - 1;
		}

		private void UpdateApplyAllButton()
		{
			applyAllButton.Enabled = applyEnabled && filters != null && filters.Count > 0;
		}

		private void UpdateBuildAllCheckBox()
		{
			bool applyAll = true;

			if (filters != null)
			{
				foreach (ITopicFilter filter in filters)
				{
					if (!filter.AutoApply)
					{
						applyAll = false;
						break;
					}
				}
			}

			buildAllCheckBox.Checked = applyAll;
		}

		private void HandleCellClick(DataGridViewCellEventArgs e)
		{
			if (e.RowIndex > -1)
			{
				if (e.ColumnIndex == autoApplyCheckBoxColumn.DisplayIndex)
					filtersDataGridView.EndEdit();
				else if (e.ColumnIndex == editButtonColumn.DisplayIndex)
				{
					ITopicFilter filter = (ITopicFilter) filtersDataGridView.Rows[e.RowIndex].DataBoundItem;

					OnEditFilter(new TopicFilterEventArgs(filter));
				}
				else if (e.ColumnIndex == applyButtonColumn.DisplayIndex)
				{
					ITopicFilter filter = (ITopicFilter) filtersDataGridView.Rows[e.RowIndex].DataBoundItem;

					OnApplyFilter(new TopicFilterEventArgs(filter));
				}
				else if (e.ColumnIndex == loadButtonColumn.DisplayIndex)
				{
					ITopicFilter filter = (ITopicFilter) filtersDataGridView.Rows[e.RowIndex].DataBoundItem;

					OnLoadFilter(new TopicFilterEventArgs(filter));
				}
			}
		}
		#endregion

		#region Events
		private readonly object ApplyFilterEvent = new object();
		private readonly object LoadFilterEvent = new object();
		private readonly object EditFilterEvent = new object();

		/// <summary>
		/// Event raised when a filter must be edited.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a filter must be edited.")]
		public event EventHandler<TopicFilterEventArgs> EditFilter
		{
			add
			{
				lock (EditFilterEvent)
				{
					Events.AddHandler(EditFilterEvent, value);
				}
			}
			remove
			{
				lock (EditFilterEvent)
				{
					Events.RemoveHandler(EditFilterEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="EditFilter" /> event.
		/// </summary>
		/// <param name="e"><see cref="TopicFilterEventArgs" /> object that provides the arguments for the event.</param>
		private void OnEditFilter(TopicFilterEventArgs e)
		{
			EventHandler<TopicFilterEventArgs> handler = null;

			lock (EditFilterEvent)
			{
				handler = (EventHandler<TopicFilterEventArgs>) Events[EditFilterEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a <see cref="TopicFilter"/> must be loaded.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a filter must be loaded.")]
		public event EventHandler<TopicFilterEventArgs> LoadFilter
		{
			add
			{
				lock (LoadFilterEvent)
				{
					Events.AddHandler(LoadFilterEvent, value);
				}
			}
			remove
			{
				lock (LoadFilterEvent)
				{
					Events.RemoveHandler(LoadFilterEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="LoadFilter" /> event.
		/// </summary>
		/// <param name="e"><see cref="TopicFilterEventArgs" /> object that provides the arguments for the event.</param>
		private void OnLoadFilter(TopicFilterEventArgs e)
		{
			EventHandler<TopicFilterEventArgs> handler = null;

			lock (LoadFilterEvent)
			{
				handler = (EventHandler<TopicFilterEventArgs>) Events[LoadFilterEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a <see cref="TopicFilter"/> must be applied.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a filter must be applied.")]
		public event EventHandler<TopicFilterEventArgs> ApplyFilter
		{
			add
			{
				lock (ApplyFilterEvent)
				{
					Events.AddHandler(ApplyFilterEvent, value);
				}
			}
			remove
			{
				lock (ApplyFilterEvent)
				{
					Events.RemoveHandler(ApplyFilterEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ApplyFilter" /> event.
		/// </summary>
		/// <param name="e"><see cref="TopicFilterEventArgs" /> object that provides the arguments for the event.</param>
		private void OnApplyFilter(TopicFilterEventArgs e)
		{
			EventHandler<TopicFilterEventArgs> handler = null;

			lock (ApplyFilterEvent)
			{
				handler = (EventHandler<TopicFilterEventArgs>) Events[ApplyFilterEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void filters_ItemPropertyChanged(object sender, TopicFilterItemPropertyChangedEventArgs e)
		{
			if (!updatingBuildStates && "AutoApply".Equals(e.PropertyName, StringComparison.Ordinal))
			{
				updatingBuildAllCheckBoxState = true;

				try
				{
					if (e.Filter.AutoApply)
						UpdateBuildAllCheckBox();
					else
						buildAllCheckBox.Checked = false;
				}
				finally
				{
					updatingBuildAllCheckBoxState = false;
				}
			}
		}

		private void source_ListChanged(object sender, ListChangedEventArgs e)
		{
			UpdateApplyAllButton();
			UpdateBuildAllCheckBox();
		}

		private void buildAllCheckBox_CheckedChanged(object sender, EventArgs e)
		{
			if (updatingBuildAllCheckBoxState)
				return;

			bool check = buildAllCheckBox.Checked;

			updatingBuildStates = true;

			SuspendChangeTracking();

			try
			{
				foreach (ITopicFilter filter in filters)
					filter.AutoApply = check;
			}
			finally
			{
				updatingBuildStates = false;
				ResumeChangeTracking();
			}
		}

		private void importButton_Click(object sender, EventArgs e)
		{
			ImportFilters();
		}

		private void applyAllButton_Click(object sender, EventArgs e)
		{
			foreach (DataGridViewRow row in filtersDataGridView.Rows)
			{
				if (row.Index > -1)
				{
					ITopicFilter filter = (ITopicFilter) row.DataBoundItem;

					OnApplyFilter(new TopicFilterEventArgs(filter));
				}
			}
		}

		private void filtersDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
		{
			HandleCellClick(e);
		}

		private void filtersDataGridView_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
		{
			HandleCellClick(e);
		}

		private void filtersDataGridView_SelectionChanged(object sender, EventArgs e)
		{
			UpdateForSelectedRow();
		}

		private void filtersDataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
		{
			DataGridViewCell cell = filtersDataGridView[e.ColumnIndex, e.RowIndex];

			if (e.ColumnIndex == applyButtonColumn.DisplayIndex)
				cell.ToolTipText = applyButtonColumn.ToolTipText;
			else if (e.ColumnIndex == loadButtonColumn.DisplayIndex)
				cell.ToolTipText = loadButtonColumn.ToolTipText;
			else if (e.ColumnIndex == includeCheckBoxColumn.DisplayIndex)
				cell.ToolTipText = includeCheckBoxColumn.ToolTipText;
			else if (e.ColumnIndex == autoApplyCheckBoxColumn.DisplayIndex)
				cell.ToolTipText = autoApplyCheckBoxColumn.ToolTipText;
		}

		private void upButton_Click(object sender, EventArgs e)
		{
			MoveSelectedFilterUp();
		}

		private void downButton_Click(object sender, EventArgs e)
		{
			MoveSelectedFilterDown();
		}

		private void deleteButton_Click(object sender, EventArgs e)
		{
			ITopicFilter filter = SelectedTopicFilter;

			if (filter != null)
				filters.Remove(filter);
		}

		private void addButton_Click(object sender, EventArgs e)
		{
			using (TopicFiltersImportDialog dialog = new TopicFiltersImportDialog())
			{
				if (dialog.ShowDialog(DocProjectEnvironment.MainWindow) == DialogResult.OK)
				{
					ImportFilters(dialog.SelectedFilterTypes);
				}
			}
		}
		#endregion
	}
}
